/*
 * date: 2009-11-23 
 * author: Maciej Smolenski <jezdnia@gmail.com>
 */
#ifndef _RPC_QUERY_HPP
#define _RPC_QUERY_HPP

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sstream>
#include <cerrno>
#include <string>
#include <rpc/rpc.h>
#include "rpc_error.hpp"

#define PORTMAP_SERVICE_NAME "sunrpc"

namespace rpc_query
{

namespace rme=rpc_error;

template <typename CT>
class rpc_query{
protected:
	std::string m_host;
	long unsigned m_program;
	long unsigned m_version;
	CLIENT *m_client;
	int m_sd;

	rpc_query(std::string p_host, long unsigned p_program, long unsigned p_version) throw (rme::rpc_error);
	virtual ~rpc_query();
};

template <typename CT>
rpc_query<CT>::rpc_query(std::string p_host, long unsigned p_program, long unsigned p_version) throw (rme::rpc_error) : m_host(p_host), m_program(p_program), m_version(p_version), m_client(NULL), m_sd(-1)
{
	struct addrinfo filter;
	struct addrinfo *addrs, *a_it;
	struct sockaddr_in pmaddr;
	int result;

	memset(&filter, 0, sizeof(filter));
	filter.ai_family = AF_INET;
	filter.ai_socktype = CT::socket_type;
	filter.ai_protocol = CT::protocol;

	if((result = getaddrinfo(m_host.c_str(), PORTMAP_SERVICE_NAME, &filter, &addrs))!=0)
		{
		std::ostringstream os;
		os << "getaddrinfo failed host: " << m_host << " error: " << gai_strerror(result);
		throw rme::rpc_error(rme::make_error_code(RPC_FAILED), os.str());
		}

	for(a_it = addrs; a_it!=NULL; a_it = a_it->ai_next)
	{
		if ((m_sd = socket(a_it->ai_family, a_it->ai_socktype, a_it->ai_protocol)) == -1)
			continue;

		if (connect(m_sd, a_it->ai_addr, a_it->ai_addrlen) == 0)
			break;

		close(m_sd);
	}
	
	if (a_it==NULL)
	{
		freeaddrinfo(addrs);
		throw rme::rpc_error(rme::make_error_code(RPC_FAILED), std::string("can't connect to rpc_service on host: ") + m_host);	
	}

	close(m_sd);
	
	memcpy(&pmaddr, a_it->ai_addr, a_it->ai_addrlen);
	freeaddrinfo(addrs);

	pmaddr.sin_port = 0;
	m_sd = RPC_ANYSOCK;
	if ((m_client = CT::create_client(&pmaddr, m_program, m_version, &m_sd)) == NULL)
	{
		std::ostringstream os;
		os << "create_client failed " << "host: " << m_host << " program: " << m_program << " version: " << m_version << " " << clnt_spcreateerror("");
		throw rme::rpc_error(rme::make_error_code(RPC_FAILED), os.str());
	}
};


template <typename CT>
rpc_query<CT>::~rpc_query()
{
	if (m_client) clnt_destroy(m_client);
}

}

#endif
